library(data.table)
library(ggplot2)
library(caret)
library(nnet)
library(pls)
library(FactoMineR)
library(qqman)
library(mixOmics)

import des datas

data(Phenotype)
Warning: data set ‘Phenotype’ not found
data(Genomic)
Warning: data set ‘Genomic’ not found
data("localisation")
Warning: data set ‘localisation’ not found
pheno <- as.data.table(Phenotype)
loc_df<- as.data.table(localisation)
geno<- as.data.table(Genomic)
geno[, ID := rownames(Genomic)]
pheno[, ID := rownames(Phenotype)]

merged<- merge(pheno, loc_df[,list(Population,ID)], by = "ID")
#merged<- merge(merged, geno, by = "ID")

LM (Pheno ~ population )

df <- as.data.table(merged)
phenos <- names(df)[sapply(df, is.numeric)]
phenos <- setdiff(phenos, c("class_index","Lat","Lgn","ID"))
phenos
 [1] "HT2009"               "CIRC2009"             "HT2011"               "CIRC2011"             "H"                   
 [6] "G"                    "S"                    "H_G"                  "S_G"                  "Klason_lignin"       
[11] "Glucose"              "Xyl_Glu"              "C5_C6"                "Wet_chem_extractives" "Dia2015_sqrt"        
[16] "InfraDens"            "Rust"                 "BrAnglVert"           "RamifSyllep"          "BudFlushSlope"       
get_r2 <- function(var) {
  form<- as.formula(paste(var, "~ Population"))
  mod<- lm(form, data = df)
  summary(mod)$r.squared
}

r2_values<- data.table(
  phenotype = phenos,
  r2= sapply(phenos, get_r2)

)

ggplot(r2_values, aes(x = reorder(phenotype, r2), y = r2)) +
  geom_point(size = 3) +
  geom_segment(aes(x = phenotype, xend = phenotype, y = 0, yend = r2)) +
  coord_flip() +
  ylab("R² lm(Phénotype ~ Population)") +
  xlab("Caractère phénotypique") +
  theme_minimal()

khi 2 snp ~ pop

table cont snp chr13 et pop => haplo pour différentier la pop

Data visualisation

Groupes de variables : chromosomes, phénotypes, populations

constructions des axes : chromosomes => on sépare les individus selon leur distances génomiques et on projette en supplémentaires les phénotypes / population

on pourra voir quels chromosomes ne sont pas dutout corrélés avec des traits phénotypiques

Réccupération des positions :

merged<- merge(merged, geno, by = "ID")
geno_mat <- as.matrix(geno)
coords <- do.call(rbind, strsplit(colnames(geno), "_"))
Warning: number of columns of result is not a multiple of vector length (arg 1)
chrom <- as.factor(coords[,1])
pos <- as.numeric(coords[,2])
Warning: NAs introduced by coercion

PCA

pca <- PCA(merged[,2:217022], quali.sup=21 ,quanti.sup=(2:20) )

plot(pca,choix="ind",habillage=21)

plot(pca,choix="var", #invisible = c("quali","var"),
     label = c("quanti.sup"),
     select = "coord 10" )

MixOmic

sPCA

X<- as.matrix(merged[,23:217022])
X2 <- X[, apply(X, 2, sd) != 0]

Y <- as.matrix(merged$CIRC2011)

result.spca.multi <- spca(X2, keepX = c(50, 30))

plotIndiv(result.spca.multi)  

plotVar(result.spca.multi)   
Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
Please use tidy evaluation idioms with `aes()`. 
See also `vignette("ggplot2-in-packages")` for more information.

# extract the variables used to construct the first PC
selectVar(result.spca.multi, comp = 1)$name 
 [1] "Chr02_6422161"  "Chr02_6427114"  "Chr02_6427264"  "Chr02_6416381"  "Chr13_15625954" "Chr13_15574007"
 [7] "Chr13_15627568" "Chr07_7181966"  "Chr13_15626544" "Chr02_6382504"  "Chr13_15635566" "Chr02_6422861" 
[13] "Chr02_6423252"  "Chr02_6426926"  "Chr12_3267410"  "Chr12_3264433"  "Chr12_3267439"  "Chr12_3264479" 
[19] "Chr10_20127228" "Chr01_42022246" "Chr17_7247760"  "Chr10_20121818" "Chr10_20126974" "Chr17_5836683" 
[25] "Chr02_6325961"  "Chr03_18516527" "Chr06_22417885" "Chr02_6332720"  "Chr11_17347389" "Chr06_24641715"
[31] "Chr11_17347361" "Chr14_13405721" "Chr11_17347960" "Chr01_14722117" "Chr06_24641246" "Chr03_11418460"
[37] "Chr03_11422148" "Chr01_14722377" "Chr03_15711505" "Chr11_4789295"  "Chr01_14717384" "Chr15_9806504" 
[43] "Chr17_14258124" "Chr17_14265429" "Chr07_12991228" "Chr07_12991382" "Chr01_8617466"  "Chr06_20423555"
[49] "Chr07_12997322" "Chr01_14708394"
# depict weight assigned to each of these variables
plotLoadings(result.spca.multi, method = 'mean', contrib = 'max')

block PLS

pls.result <- mixOmics::pls(X, Y) 
Warning: the standard deviation is zero
plotIndiv(pls.result) 

plotIndiv(pls.result, group = merged$Population, 
          rep.space = 'XY-variate', 
          ellipse = TRUE,  # plot using the ellipses
          legend = TRUE)

block sPLS

head(merged[,2:21])
Y <- as.matrix(merged[,2:21])

spls.result <- mixOmics::spls(X2, Y, ncomp=5, keepX = c(rep(1000,5))) 

plotIndiv(spls.result, group = merged$Population, 
          rep.space = 'XY-variate', 
          ellipse = TRUE,  # plot using the ellipses
          legend = TRUE)

# Identifier les coefficients non nuls
nonzero_idx <- loadX != 0
Error: object 'loadX' not found

manhattan plot

manhattan(merged_nonzero, chr = "1", bp = "2", p = "loading",snp = "SNP",
          col = c("blue4", "orange3"), 
          cex = 0.6)  # taille des points

block PLS_DA

# use the mirna, mrna and protein expression levels as predictive datasets
# note that each dataset is measured across the same individuals (samples)

X1<- as.matrix(merged[,23:217022])
X1 <- X1[, apply(X1, 2, sd) != 0]
X2 <- as.matrix(merged[,2:21])

Y <- as.factor(merged$Population)

X <- list(geno = X1, pheno = X2)
result.diablo.tcga <- block.plsda(X, Y) # run the method

plotIndiv(result.diablo.tcga,
          rep.space = 'XY-variate', 
          ellipse = TRUE,  # plot using the ellipses
          legend = TRUE)

Block sPLS DA

# set the number of features to use for the X datasets
list.keepX = list(geno = c(rep(10000,5)), pheno = c(rep(20,5)))
# run the method
result.sparse.diablo.tcga <-  block.splsda(X, Y, keepX = list.keepX, ncomp = 5)
plotLoadings(result.sparse.diablo.tcga, ncomp = 5)

Chr13_15574007 ressort encore

plotIndiv(result.sparse.diablo.tcga,ellipse = TRUE,legend = TRUE) 

plotVar(result.sparse.diablo.tcga) # plot the variables

Boostrapped sPLS

library(spls)
y <- merged$CIRC2009  
y <- as.numeric(y)
X <- as.matrix(merged[,23:217022])
# Split stratifié 
# Ici, on fait un split simple car phénotype continu
train_index <- createDataPartition(y, p = 0.9, list = FALSE)

X_train <- X[train_index, ]
var_threshold <- 1e-8

nonzero_cols <- apply(X_train, 2, var) > var_threshold

length(nonzero_cols)
X_train <- X_train[, nonzero_cols]

X_test  <- X[-train_index, ]
X_test  <- X_test[, nonzero_cols]


y_train <- y[train_index]

y_test  <- y[-train_index]
eta = seq(0.1,0.9,0.1)
K = c(1:5)
cv.spls( X_train, y_train, fold=5, K, eta, kappa=0.5, select="pls2", fit="simpls",scale.x=FALSE, scale.y=FALSE, plot.it=TRUE )
model <- spls(X_train, y_train, K = 4, eta =0.2 , kappa=0.5, select="pls2", fit="simpls",
scale.x=FALSE, scale.y=FALSE, eps=1e-4, maxstep=100, trace=FALSE)
ped <- predict.spls( model, X_test, type = c("fit","coefficient"))
ped
f <- spls::ci.spls( model, coverage=0.95, B=1000,
                    plot.it=TRUE, plot.fix="y",
                    plot.var=NA, K=model$K, fit=model$fit )
names(f)
nonzero_idx <- which(f$betahat != 0)
betahat_nonzero <- f$betahat[nonzero_idx]
length(nonzero_idx)

MFA

res = MFA(geno_mat, group=c(25974,16646,12284,11790,14769,17004,8327,14040,9840,15964,7483,7403,8040,11123,8486,7629,6747,8267,4876,308), type=c(rep("s",20)), ncp=5, name.group=c("Chr01","Chr02","Chr03","Chr04", "Chr05","Chr06","Chr07","Chr08","Chr09","Chr10", "Chr11","Chr12","Chr13", "Chr14","Chr15","Chr16","Chr17","Chr18","Chr19", "scaffold"))
dim(geno_mat)

PLS : Phenotype ~ all SNP

inner_train_index <- createDataPartition(y_train, p = 0.9, list = FALSE)
X_train <- X_train_df[inner_train_index, , drop = FALSE]
y_train <- y_train[inner_train_index]
X_test <- X_train_df[-inner_train_index, , drop = FALSE]
y_test <- y_train[-inner_train_index]
df_train <- data.frame(y = y_train, X_train_df)
pls_final <- plsr(y ~ ., data = df_train, ncomp = ncomp_opt, validation = "none")
  
  # Évaluation sur le fold externe 
  
y_pred_final <- as.vector(predict(pls_final, newdata = X_test_df, ncomp = ncomp_opt))

PLS Subset SNP | calcul de VIP | nested CV

\[VIP_{j} = \sqrt{ p \frac{\displaystyle \sum_{h=1}^{A} SS_{Y,h}\frac{w_{jh}^{2}}{\lVert w_{h} \rVert^{2}}}{\displaystyle \sum_{h=1}^{A} SS_{Y,h}} }\]

set.seed(123)
# Paramètres
n_iter <- 400
subset_size <- 10000     # nombre de SNP aléatoires par itération
ncomp_candidates <- 1:5  # candidats pour ncomp
nfold_outer <- 5
nfold_inner <- 10
results_perf
vip_perf<- results_perf[, .(R2_test_mean = mean(R2_test)), by = iter]
vip_perf_fold<- results_perf[, .(R2_test_mean = mean(R2_test)), by = fold]
ggplot(results_perf[1:1000,], aes(x = factor(iter), y = R2_test)) +
  geom_violin(fill = "skyblue", color = "black") +
  labs(x = "Itération", y = expression(R^2), title = "R² 10-outer-fold ") +
  theme_minimal()
mean(vip_perf_fold$R2_test_mean)
vip_perf_fold
library(data.table)
library(ggplot2)


# Calcul du VIP moyen par SNP
vip_mean<- results_vip[, .(VIP_mean = mean(VIP),SNP_count = .N), by = SNP]
# Sélection des 30 SNP ayant le VIP moyen le plus élevé
topN<- vip_mean[order(-VIP_mean)][0:5000, SNP]

df_topNPLS<- results_vip[SNP %in% topN]
df_topNPLS <- merge(df_topNPLS, vip_mean[, .(SNP, SNP_count)], by = "SNP")
df_topNPLS$SNP_count <- df_topNPLS$SNP_count/10
ggplot(df_topNPLS, aes(x = reorder(SNP,VIP), y = VIP, fill = SNP_count)) +
  geom_violin(trim = FALSE) +
  geom_boxplot(width = 0.1, outlier.size = 0.5) +
  scale_fill_gradient(low = "lightblue", high = "darkblue") +
  theme_bw() +
  theme(
    axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)
  ) +
  labs(
    x = "SNP",
    y = "VIP",
    title = "Distribution du VIP pour les n = 50 SNP les plus importants",
    fill = "Nbr de selection du SNP"
  )
hist(vip_mean$VIP_mean, breaks = seq(min(vip_mean$VIP_mean), max(vip_mean$VIP_mean), length.out = 50),
     main = "Distribution des scores VIP moyens",
     xlab = "scores VIP moyens", col = "lightblue", border = "white")

Lasso

library(glmnet)

# Assurer que y est numérique et X est une matrice
y <- as.numeric(pheno$CIRC2009)
X <- as.matrix(geno)

train_index <- createDataPartition(y, p = 0.8, list = FALSE)
X_train <- X[train_index, ]
X_test  <- X[-train_index, ]
y_train <- y[train_index]
y_test  <- y[-train_index]

# Ajustement du Lasso avec validation croisée pour choisir lambda
set.seed(123)

cv_lasso <- cv.glmnet(X_train, y_train, alpha = 1, nfolds = 5)

# Afficher la meilleure valeur de lambda
best_lambda <- cv_lasso$lambda.min
print(best_lambda)

# Tracer l'erreur de validation croisée
plot(cv_lasso)

# Ajuster le modèle final avec lambda optimal
lasso_model <- glmnet(X_train, y_train, alpha = 1, lambda = best_lambda)

# Extraire les coefficients
coef(lasso_model)
print(best_lambda)
y_pred <- predict(lasso_model, newx = X_test)

# Calcul du R²
SSE <- sum((y_test - y_pred)^2)           # somme des carrés des erreurs
SST <- sum((y_test - mean(y))^2)         # somme totale des carrés
R2 <- 1 - SSE/SST
print(sqrt(SSE))
R2
coef_df <- data.frame(
  SNP = rownames(coef(lasso_model)),
  Coefficient = as.numeric(coef(lasso_model))
)
coef_nonzero <- coef_df[coef_df$Coefficient != 0, ]
coef_nonzero_dt <- as.data.table(coef_nonzero)

# Top N SNP par valeur absolue des coefficients
N <- 50
coef_nonzero_dt[, abs_coef := abs(Coefficient)]
topN_Lasso <- coef_nonzero_dt[order(-abs_coef)][1:N, .(SNP, Coefficient)]

topN_Lasso

Comparaison selection Lasso et PLS


common_snps <- intersect(df_topNPLS$SNP, coef_nonzero$SNP)

# SNP communs
common_snps
length(common_snps)

réegression ridge adaptative

y <- as.numeric(pheno$CIRC2009)
X <- as.matrix(geno)

# Split externe train/test
set.seed(123)
train_index <- createDataPartition(y, p = 0.9, list = FALSE)
X_train <- X[train_index, ]
X_test  <- X[-train_index, ]
y_train <- y[train_index]
y_test  <- y[-train_index]

# Split interne du train en D1 et D2 : 50/50
set.seed(123)
idx_D1 <- createDataPartition(y_train, p = 0.5, list = FALSE)

X_D1 <- X_train[idx_D1, ]
X_D2 <- X_train[-idx_D1, ]
y_D1 <- y_train[idx_D1]
y_D2 <- y_train[-idx_D1]

STEP I : SCREENING (Elastic Net sur D1)

alpha <- 1 # Elastic Net si alpha = 0.5, Lasso si = 1, ridge si =0 
set.seed(123)
cv_enet <- cv.glmnet(
  X_D1, y_D1,
  alpha = alpha,               
  nfolds = 10,
  standardize = TRUE
)

lambda_hat <- cv_enet$lambda.min
lambda_hat
# Ajustement final 
enet_model <- glmnet(X_D1, y_D1, alpha = alpha, lambda = 0.002)


coefs <- coef(enet_model)
selected <- which(coefs[-1] != 0)          # indices des SNP sélectionnés
S_hat <- selected

cat("Nombre de variables sélectionnées (screening):", length(S_hat), "\n")

# Poids dérivés des coefficients Elastic Net
beta_hat <- as.numeric(coefs[-1])
weights <- abs(beta_hat)
weights <- weights[S_hat]
weights <- weights / max(weights)          # normalisation
lambda_hat <- 0.002

STEP II : CLEANING (Ridge sur D2)

X_D2_sel <- X_D2[, S_hat, drop = FALSE]

# Pondération Ridge : mettre plus de pénalité sur les petites β_EN
ridge_penalty <- 1 / (weights + 1e-6)

set.seed(123)
cv_ridge <- cv.glmnet(
  X_D2_sel, y_D2,
  alpha = 0,                     # alpha = 0 → Ridge
  nfolds = 10,
  penalty.factor = ridge_penalty
)

lambda_ridge <- cv_ridge$lambda.min

# Ajustement final sur D2
ridge_model <- glmnet(
  X_D2_sel, y_D2,
  alpha = 0,
  lambda = lambda_ridge,
  penalty.factor = ridge_penalty
)

# Coefficients ridge finaux (sur D2)
beta_clean <- coef(ridge_model)

STEP III : REFIT FINAL SUR D = D1 ∪ D2 (train complet)

X_train_sel <- X_train[, S_hat, drop = FALSE]

ridge_final <- glmnet(
  X_train_sel, y_train,
  alpha = 0,
  lambda = lambda_ridge,
  penalty.factor = ridge_penalty
)

# Prédictions sur le test externe
X_test_sel <- X_test[, S_hat, drop = FALSE]
y_pred <- predict(ridge_final, newx = X_test_sel)

# R² externe
R2_test <- 1 - sum((y_test - y_pred)^2) / sum((y_test - mean(y_test))^2)
cat("R2 externe :", R2_test, "\n")
LS0tCnRpdGxlOiAiQ2x1c3RlcmluZyBMRCBwaGVub3R5cGlxdWUgYmFzZWQiCmZvcm1hdDogaHRtbApvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKZWRpdG9yOiB2aXN1YWwKLS0tCgpgYGB7cn0KbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkobm5ldCkKbGlicmFyeShwbHMpCmxpYnJhcnkoRmFjdG9NaW5lUikKbGlicmFyeShxcW1hbikKbGlicmFyeShtaXhPbWljcykKCmBgYAoKIyBpbXBvcnQgZGVzIGRhdGFzCgpgYGB7cn0KZGF0YShQaGVub3R5cGUpCmRhdGEoR2Vub21pYykKZGF0YSgibG9jYWxpc2F0aW9uIikKYGBgCgpgYGB7cn0KcGhlbm8gPC0gYXMuZGF0YS50YWJsZShQaGVub3R5cGUpCmxvY19kZjwtIGFzLmRhdGEudGFibGUobG9jYWxpc2F0aW9uKQpnZW5vPC0gYXMuZGF0YS50YWJsZShHZW5vbWljKQpnZW5vWywgSUQgOj0gcm93bmFtZXMoR2Vub21pYyldCnBoZW5vWywgSUQgOj0gcm93bmFtZXMoUGhlbm90eXBlKV0KCm1lcmdlZDwtIG1lcmdlKHBoZW5vLCBsb2NfZGZbLGxpc3QoUG9wdWxhdGlvbixJRCldLCBieSA9ICJJRCIpCgpgYGAKCiMgTE0gKFBoZW5vIFx+IHBvcHVsYXRpb24gKQoKYGBge3J9CmRmIDwtIGFzLmRhdGEudGFibGUobWVyZ2VkKQpgYGAKCgoKYGBge3J9CnBoZW5vcyA8LSBuYW1lcyhkZilbc2FwcGx5KGRmLCBpcy5udW1lcmljKV0KcGhlbm9zIDwtIHNldGRpZmYocGhlbm9zLCBjKCJjbGFzc19pbmRleCIsIkxhdCIsIkxnbiIsIklEIikpCmBgYAoKYGBge3J9CnBoZW5vcwpgYGAKCgpgYGB7cn0KZ2V0X3IyIDwtIGZ1bmN0aW9uKHZhcikgewogIGZvcm08LSBhcy5mb3JtdWxhKHBhc3RlKHZhciwgIn4gUG9wdWxhdGlvbiIpKQogIG1vZDwtIGxtKGZvcm0sIGRhdGEgPSBkZikKICBzdW1tYXJ5KG1vZCkkci5zcXVhcmVkCn0KCnIyX3ZhbHVlczwtIGRhdGEudGFibGUoCiAgcGhlbm90eXBlID0gcGhlbm9zLAogIHIyPSBzYXBwbHkocGhlbm9zLCBnZXRfcjIpKQpgYGAKCmBgYHtyfQoKZ2dwbG90KHIyX3ZhbHVlcywgYWVzKHggPSByZW9yZGVyKHBoZW5vdHlwZSwgcjIpLCB5ID0gcjIpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMykgKwogIGdlb21fc2VnbWVudChhZXMoeCA9IHBoZW5vdHlwZSwgeGVuZCA9IHBoZW5vdHlwZSwgeSA9IDAsIHllbmQgPSByMikpICsKICBjb29yZF9mbGlwKCkgKwogIHlsYWIoIlLCsiBsbShQaMOpbm90eXBlIH4gUG9wdWxhdGlvbikiKSArCiAgeGxhYigiQ2FyYWN0w6hyZSBwaMOpbm90eXBpcXVlIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKa2hpIDIgc25wIH4gcG9wIAoKdGFibGUgY29udCBzbnAgY2hyMTMgZXQgcG9wID0+IGhhcGxvIHBvdXIgZGlmZsOpcmVudGllciBsYSBwb3AgCgojIERhdGEgdmlzdWFsaXNhdGlvbgoKR3JvdXBlcyBkZSB2YXJpYWJsZXMgOiBjaHJvbW9zb21lcywgcGjDqW5vdHlwZXMsIHBvcHVsYXRpb25zCgpjb25zdHJ1Y3Rpb25zIGRlcyBheGVzIDogY2hyb21vc29tZXMgPVw+IG9uIHPDqXBhcmUgbGVzIGluZGl2aWR1cyBzZWxvbiBsZXVyIGRpc3RhbmNlcyBnw6lub21pcXVlcyBldCBvbiBwcm9qZXR0ZSBlbiBzdXBwbMOpbWVudGFpcmVzIGxlcyBwaMOpbm90eXBlcyAvIHBvcHVsYXRpb24KCm9uIHBvdXJyYSB2b2lyIHF1ZWxzIGNocm9tb3NvbWVzIG5lIHNvbnQgcGFzIGR1dG91dCBjb3Jyw6lsw6lzIGF2ZWMgZGVzIHRyYWl0cyBwaMOpbm90eXBpcXVlcwoKIyBSw6ljY3Vww6lyYXRpb24gZGVzIHBvc2l0aW9ucyA6CgpgYGB7cn0KbWVyZ2VkPC0gbWVyZ2UobWVyZ2VkLCBnZW5vLCBieSA9ICJJRCIpCmdlbm9fbWF0IDwtIGFzLm1hdHJpeChnZW5vKQpjb29yZHMgPC0gZG8uY2FsbChyYmluZCwgc3Ryc3BsaXQoY29sbmFtZXMoZ2VubyksICJfIikpCmNocm9tIDwtIGFzLmZhY3Rvcihjb29yZHNbLDFdKQpwb3MgPC0gYXMubnVtZXJpYyhjb29yZHNbLDJdKQpgYGAKCiMjIFBDQQoKYGBge3J9CnBjYSA8LSBQQ0EobWVyZ2VkWywyOjIxNzAyMl0sIHF1YWxpLnN1cD0yMSAscXVhbnRpLnN1cD0oMjoyMCkgKQpgYGAKCmBgYHtyfQpwbG90KHBjYSxjaG9peD0iaW5kIixoYWJpbGxhZ2U9MjEpCmBgYAoKYGBge3J9CnBsb3QocGNhLGNob2l4PSJ2YXIiLCAjaW52aXNpYmxlID0gYygicXVhbGkiLCJ2YXIiKSwKICAgICBsYWJlbCA9IGMoInF1YW50aS5zdXAiKSwKICAgICBzZWxlY3QgPSAiY29vcmQgMTAiICkKYGBgCgojIyBNaXhPbWljCgojIyMgc1BDQQoKYGBge3J9Clg8LSBhcy5tYXRyaXgobWVyZ2VkWywyMzoyMTcwMjJdKQpYMiA8LSBYWywgYXBwbHkoWCwgMiwgc2QpICE9IDBdCgpZIDwtIGFzLm1hdHJpeChtZXJnZWQkQ0lSQzIwMTEpCgpyZXN1bHQuc3BjYS5tdWx0aSA8LSBzcGNhKFgyLCBrZWVwWCA9IGMoNTAsIDMwKSkKCnBsb3RJbmRpdihyZXN1bHQuc3BjYS5tdWx0aSkgIApwbG90VmFyKHJlc3VsdC5zcGNhLm11bHRpKSAgIAoKIyBleHRyYWN0IHRoZSB2YXJpYWJsZXMgdXNlZCB0byBjb25zdHJ1Y3QgdGhlIGZpcnN0IFBDCnNlbGVjdFZhcihyZXN1bHQuc3BjYS5tdWx0aSwgY29tcCA9IDEpJG5hbWUgCiMgZGVwaWN0IHdlaWdodCBhc3NpZ25lZCB0byBlYWNoIG9mIHRoZXNlIHZhcmlhYmxlcwpwbG90TG9hZGluZ3MocmVzdWx0LnNwY2EubXVsdGksIG1ldGhvZCA9ICdtZWFuJywgY29udHJpYiA9ICdtYXgnKQpgYGAKCiMjIyBibG9jayBQTFMKCmBgYHtyfQpwbHMucmVzdWx0IDwtIG1peE9taWNzOjpwbHMoWCwgWSkgCnBsb3RJbmRpdihwbHMucmVzdWx0KSAKYGBgCgpgYGB7cn0KcGxvdEluZGl2KHBscy5yZXN1bHQsIGdyb3VwID0gbWVyZ2VkJFBvcHVsYXRpb24sIAogICAgICAgICAgcmVwLnNwYWNlID0gJ1hZLXZhcmlhdGUnLCAKICAgICAgICAgIGVsbGlwc2UgPSBUUlVFLCAgIyBwbG90IHVzaW5nIHRoZSBlbGxpcHNlcwogICAgICAgICAgbGVnZW5kID0gVFJVRSkKYGBgCgojIyMgYmxvY2sgc1BMUwoKYGBge3J9CmhlYWQobWVyZ2VkWywyOjIxXSkKYGBgCgpgYGB7cn0KWSA8LSBhcy5tYXRyaXgobWVyZ2VkWywyOjIxXSkKCnNwbHMucmVzdWx0IDwtIG1peE9taWNzOjpzcGxzKFgyLCBZLCBuY29tcD01LCBrZWVwWCA9IGMocmVwKDEwMDAsNSkpKSAKCnBsb3RJbmRpdihzcGxzLnJlc3VsdCwgZ3JvdXAgPSBtZXJnZWQkUG9wdWxhdGlvbiwgCiAgICAgICAgICByZXAuc3BhY2UgPSAnWFktdmFyaWF0ZScsIAogICAgICAgICAgZWxsaXBzZSA9IFRSVUUsICAjIHBsb3QgdXNpbmcgdGhlIGVsbGlwc2VzCiAgICAgICAgICBsZWdlbmQgPSBUUlVFKQpgYGAKCmBgYHtyfQojIElkZW50aWZpZXIgbGVzIGNvZWZmaWNpZW50cyBub24gbnVscwpub256ZXJvX2lkeCA8LSBsb2FkWCAhPSAwCmRmX25vbnplcm9fWCA8LSBkYXRhLmZyYW1lKAogIFNOUCA9IG5hbWVzKGxvYWRYKVtub256ZXJvX2lkeF0sCiAgbG9hZGluZyAgPSBsb2FkWFtub256ZXJvX2lkeF0KKQpjb29yZHMgPC0gZG8uY2FsbChyYmluZCwgc3Ryc3BsaXQocm93bmFtZXMoZGZfbm9uemVyb19YKSwgIl8iKSkKY29vcmRzWywxXSA8LSBhcy5udW1lcmljKHN1YigiQ2hyIiwgIiIsIGNvb3Jkc1ssMV0pKQpwb3MgPC0gYXMubnVtZXJpYyhjb29yZHNbLDJdKQptZXJnZWRfbm9uemVybyA8LSBjYmluZChjb29yZHMsZGZfbm9uemVyb19YKQptZXJnZWRfbm9uemVybyQiMiIgPC0gYXMubnVtZXJpYyhtZXJnZWRfbm9uemVybyQiMiIpCm1lcmdlZF9ub256ZXJvJCIxIiA8LSBhcy5udW1lcmljKG1lcmdlZF9ub256ZXJvJCIxIikKbWVyZ2VkX25vbnplcm8kbG9hZGluZyA8LSBhYnMobWVyZ2VkX25vbnplcm8kbG9hZGluZykKYGBgCgojIG1hbmhhdHRhbiBwbG90CgpgYGB7cn0KbWFuaGF0dGFuKG1lcmdlZF9ub256ZXJvLCBjaHIgPSAiMSIsIGJwID0gIjIiLCBwID0gImxvYWRpbmciLHNucCA9ICJTTlAiLAogICAgICAgICAgY29sID0gYygiYmx1ZTQiLCAib3JhbmdlMyIpLCAKICAgICAgICAgIGNleCA9IDAuNikgICMgdGFpbGxlIGRlcyBwb2ludHMKYGBgCgojIyMgYmxvY2sgUExTX0RBCgpgYGB7cn0KIyB1c2UgdGhlIG1pcm5hLCBtcm5hIGFuZCBwcm90ZWluIGV4cHJlc3Npb24gbGV2ZWxzIGFzIHByZWRpY3RpdmUgZGF0YXNldHMKIyBub3RlIHRoYXQgZWFjaCBkYXRhc2V0IGlzIG1lYXN1cmVkIGFjcm9zcyB0aGUgc2FtZSBpbmRpdmlkdWFscyAoc2FtcGxlcykKClgxPC0gYXMubWF0cml4KG1lcmdlZFssMjM6MjE3MDIyXSkKWDEgPC0gWDFbLCBhcHBseShYMSwgMiwgc2QpICE9IDBdClgyIDwtIGFzLm1hdHJpeChtZXJnZWRbLDI6MjFdKQoKWSA8LSBhcy5mYWN0b3IobWVyZ2VkJFBvcHVsYXRpb24pCgpYIDwtIGxpc3QoZ2VubyA9IFgxLCBwaGVubyA9IFgyKQoKYGBgCgpgYGB7cn0KcmVzdWx0LmRpYWJsby50Y2dhIDwtIGJsb2NrLnBsc2RhKFgsIFkpICMgcnVuIHRoZSBtZXRob2QKCnBsb3RJbmRpdihyZXN1bHQuZGlhYmxvLnRjZ2EsCiAgICAgICAgICByZXAuc3BhY2UgPSAnWFktdmFyaWF0ZScsIAogICAgICAgICAgZWxsaXBzZSA9IFRSVUUsICAjIHBsb3QgdXNpbmcgdGhlIGVsbGlwc2VzCiAgICAgICAgICBsZWdlbmQgPSBUUlVFKQpgYGAKCiMjIyBCbG9jayBzUExTIERBCgpgYGB7cn0KIyBzZXQgdGhlIG51bWJlciBvZiBmZWF0dXJlcyB0byB1c2UgZm9yIHRoZSBYIGRhdGFzZXRzCmxpc3Qua2VlcFggPSBsaXN0KGdlbm8gPSBjKHJlcCgxMDAwMCw1KSksIHBoZW5vID0gYyhyZXAoMjAsNSkpKQojIHJ1biB0aGUgbWV0aG9kCnJlc3VsdC5zcGFyc2UuZGlhYmxvLnRjZ2EgPC0gIGJsb2NrLnNwbHNkYShYLCBZLCBrZWVwWCA9IGxpc3Qua2VlcFgsIG5jb21wID0gNSkKYGBgCgpgYGB7cn0KcGxvdExvYWRpbmdzKHJlc3VsdC5zcGFyc2UuZGlhYmxvLnRjZ2EsIG5jb21wID0gNSkKYGBgCgpDaHIxM18xNTU3NDAwNyByZXNzb3J0IGVuY29yZQoKYGBge3J9CnBsb3RJbmRpdihyZXN1bHQuc3BhcnNlLmRpYWJsby50Y2dhLGVsbGlwc2UgPSBUUlVFLGxlZ2VuZCA9IFRSVUUpIApgYGAKCmBgYHtyfQpwbG90VmFyKHJlc3VsdC5zcGFyc2UuZGlhYmxvLnRjZ2EpICMgcGxvdCB0aGUgdmFyaWFibGVzCmBgYAoKIyMjIEJvb3N0cmFwcGVkIHNQTFMKCmBgYHtyfQpsaWJyYXJ5KHNwbHMpCnkgPC0gbWVyZ2VkJENJUkMyMDA5ICAKeSA8LSBhcy5udW1lcmljKHkpClggPC0gYXMubWF0cml4KG1lcmdlZFssMjM6MjE3MDIyXSkKYGBgCgpgYGB7cn0KIyBTcGxpdCBzdHJhdGlmacOpIAojIEljaSwgb24gZmFpdCB1biBzcGxpdCBzaW1wbGUgY2FyIHBow6lub3R5cGUgY29udGludQp0cmFpbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHksIHAgPSAwLjksIGxpc3QgPSBGQUxTRSkKClhfdHJhaW4gPC0gWFt0cmFpbl9pbmRleCwgXQp2YXJfdGhyZXNob2xkIDwtIDFlLTgKCm5vbnplcm9fY29scyA8LSBhcHBseShYX3RyYWluLCAyLCB2YXIpID4gdmFyX3RocmVzaG9sZAoKbGVuZ3RoKG5vbnplcm9fY29scykKWF90cmFpbiA8LSBYX3RyYWluWywgbm9uemVyb19jb2xzXQoKWF90ZXN0ICA8LSBYWy10cmFpbl9pbmRleCwgXQpYX3Rlc3QgIDwtIFhfdGVzdFssIG5vbnplcm9fY29sc10KCgp5X3RyYWluIDwtIHlbdHJhaW5faW5kZXhdCgp5X3Rlc3QgIDwtIHlbLXRyYWluX2luZGV4XQpgYGAKCmBgYHtyfQpldGEgPSBzZXEoMC4xLDAuOSwwLjEpCksgPSBjKDE6NSkKY3Yuc3BscyggWF90cmFpbiwgeV90cmFpbiwgZm9sZD01LCBLLCBldGEsIGthcHBhPTAuNSwgc2VsZWN0PSJwbHMyIiwgZml0PSJzaW1wbHMiLHNjYWxlLng9RkFMU0UsIHNjYWxlLnk9RkFMU0UsIHBsb3QuaXQ9VFJVRSApCmBgYAoKYGBge3J9Cm1vZGVsIDwtIHNwbHMoWF90cmFpbiwgeV90cmFpbiwgSyA9IDQsIGV0YSA9MC4yICwga2FwcGE9MC41LCBzZWxlY3Q9InBsczIiLCBmaXQ9InNpbXBscyIsCnNjYWxlLng9RkFMU0UsIHNjYWxlLnk9RkFMU0UsIGVwcz0xZS00LCBtYXhzdGVwPTEwMCwgdHJhY2U9RkFMU0UpCmBgYAoKYGBge3J9CnBlZCA8LSBwcmVkaWN0LnNwbHMoIG1vZGVsLCBYX3Rlc3QsIHR5cGUgPSBjKCJmaXQiLCJjb2VmZmljaWVudCIpKQpgYGAKCmBgYHtyfQpwZWQKYGBgCgpgYGB7cn0KZiA8LSBzcGxzOjpjaS5zcGxzKCBtb2RlbCwgY292ZXJhZ2U9MC45NSwgQj0xMDAwLAogICAgICAgICAgICAgICAgICAgIHBsb3QuaXQ9VFJVRSwgcGxvdC5maXg9InkiLAogICAgICAgICAgICAgICAgICAgIHBsb3QudmFyPU5BLCBLPW1vZGVsJEssIGZpdD1tb2RlbCRmaXQgKQoKCmBgYAoKYGBge3J9Cm5hbWVzKGYpCmBgYAoKYGBge3J9Cm5vbnplcm9faWR4IDwtIHdoaWNoKGYkYmV0YWhhdCAhPSAwKQpiZXRhaGF0X25vbnplcm8gPC0gZiRiZXRhaGF0W25vbnplcm9faWR4XQpgYGAKCmBgYHtyfQpsZW5ndGgobm9uemVyb19pZHgpCmBgYAoKIyMgTUZBCgpgYGB7cn0KcmVzID0gTUZBKGdlbm9fbWF0LCBncm91cD1jKDI1OTc0LDE2NjQ2LDEyMjg0LDExNzkwLDE0NzY5LDE3MDA0LDgzMjcsMTQwNDAsOTg0MCwxNTk2NCw3NDgzLDc0MDMsODA0MCwxMTEyMyw4NDg2LDc2MjksNjc0Nyw4MjY3LDQ4NzYsMzA4KSwgdHlwZT1jKHJlcCgicyIsMjApKSwgbmNwPTUsIG5hbWUuZ3JvdXA9YygiQ2hyMDEiLCJDaHIwMiIsIkNocjAzIiwiQ2hyMDQiLCAiQ2hyMDUiLCJDaHIwNiIsIkNocjA3IiwiQ2hyMDgiLCJDaHIwOSIsIkNocjEwIiwgIkNocjExIiwiQ2hyMTIiLCJDaHIxMyIsICJDaHIxNCIsIkNocjE1IiwiQ2hyMTYiLCJDaHIxNyIsIkNocjE4IiwiQ2hyMTkiLCAic2NhZmZvbGQiKSkKYGBgCgpgYGB7cn0KZGltKGdlbm9fbWF0KQpgYGAKCiMgUExTIDogUGhlbm90eXBlIFx+IGFsbCBTTlAKCmBgYHtyfQppbm5lcl90cmFpbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHlfdHJhaW4sIHAgPSAwLjksIGxpc3QgPSBGQUxTRSkKWF90cmFpbiA8LSBYX3RyYWluX2RmW2lubmVyX3RyYWluX2luZGV4LCAsIGRyb3AgPSBGQUxTRV0KeV90cmFpbiA8LSB5X3RyYWluW2lubmVyX3RyYWluX2luZGV4XQpYX3Rlc3QgPC0gWF90cmFpbl9kZlstaW5uZXJfdHJhaW5faW5kZXgsICwgZHJvcCA9IEZBTFNFXQp5X3Rlc3QgPC0geV90cmFpblstaW5uZXJfdHJhaW5faW5kZXhdCmBgYAoKCmBgYHtyfQpkZl90cmFpbiA8LSBkYXRhLmZyYW1lKHkgPSB5X3RyYWluLCBYX3RyYWluX2RmKQpwbHNfZmluYWwgPC0gcGxzcih5IH4gLiwgZGF0YSA9IGRmX3RyYWluLCBuY29tcCA9IG5jb21wX29wdCwgdmFsaWRhdGlvbiA9ICJub25lIikKICAKICAjIMOJdmFsdWF0aW9uIHN1ciBsZSBmb2xkIGV4dGVybmUgCiAgCnlfcHJlZF9maW5hbCA8LSBhcy52ZWN0b3IocHJlZGljdChwbHNfZmluYWwsIG5ld2RhdGEgPSBYX3Rlc3RfZGYsIG5jb21wID0gbmNvbXBfb3B0KSkKYGBgCgoKIyBQTFMgU3Vic2V0IFNOUCBcfCBjYWxjdWwgZGUgVklQIFx8IG5lc3RlZCBDVgoKJCRWSVBfe2p9ID0gXHNxcnR7IHAgXGZyYWN7XGRpc3BsYXlzdHlsZSBcc3VtX3toPTF9XntBfSBTU197WSxofVxmcmFje3dfe2pofV57Mn19e1xsVmVydCB3X3tofSBcclZlcnReezJ9fX17XGRpc3BsYXlzdHlsZSBcc3VtX3toPTF9XntBfSBTU197WSxofX0gfSQkCgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQojIFBhcmFtw6h0cmVzCm5faXRlciA8LSA0MDAKc3Vic2V0X3NpemUgPC0gMTAwMDAgICAgICMgbm9tYnJlIGRlIFNOUCBhbMOpYXRvaXJlcyBwYXIgaXTDqXJhdGlvbgpuY29tcF9jYW5kaWRhdGVzIDwtIDE6NSAgIyBjYW5kaWRhdHMgcG91ciBuY29tcApuZm9sZF9vdXRlciA8LSA1Cm5mb2xkX2lubmVyIDwtIDEwCmBgYAoKYGBge3J9CnJlc3VsdHNfcGVyZgp2aXBfcGVyZjwtIHJlc3VsdHNfcGVyZlssIC4oUjJfdGVzdF9tZWFuID0gbWVhbihSMl90ZXN0KSksIGJ5ID0gaXRlcl0KdmlwX3BlcmZfZm9sZDwtIHJlc3VsdHNfcGVyZlssIC4oUjJfdGVzdF9tZWFuID0gbWVhbihSMl90ZXN0KSksIGJ5ID0gZm9sZF0KYGBgCgpgYGB7cn0KZ2dwbG90KHJlc3VsdHNfcGVyZlsxOjEwMDAsXSwgYWVzKHggPSBmYWN0b3IoaXRlciksIHkgPSBSMl90ZXN0KSkgKwogIGdlb21fdmlvbGluKGZpbGwgPSAic2t5Ymx1ZSIsIGNvbG9yID0gImJsYWNrIikgKwogIGxhYnMoeCA9ICJJdMOpcmF0aW9uIiwgeSA9IGV4cHJlc3Npb24oUl4yKSwgdGl0bGUgPSAiUsKyIDEwLW91dGVyLWZvbGQgIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCmBgYHtyfQptZWFuKHZpcF9wZXJmX2ZvbGQkUjJfdGVzdF9tZWFuKQpgYGAKCmBgYHtyfQp2aXBfcGVyZl9mb2xkCmBgYAoKYGBge3J9CmxpYnJhcnkoZGF0YS50YWJsZSkKbGlicmFyeShnZ3Bsb3QyKQoKCiMgQ2FsY3VsIGR1IFZJUCBtb3llbiBwYXIgU05QCnZpcF9tZWFuPC0gcmVzdWx0c192aXBbLCAuKFZJUF9tZWFuID0gbWVhbihWSVApLFNOUF9jb3VudCA9IC5OKSwgYnkgPSBTTlBdCmBgYAoKYGBge3J9CiMgU8OpbGVjdGlvbiBkZXMgMzAgU05QIGF5YW50IGxlIFZJUCBtb3llbiBsZSBwbHVzIMOpbGV2w6kKdG9wTjwtIHZpcF9tZWFuW29yZGVyKC1WSVBfbWVhbildWzA6NTAwMCwgU05QXQoKZGZfdG9wTlBMUzwtIHJlc3VsdHNfdmlwW1NOUCAlaW4lIHRvcE5dCmRmX3RvcE5QTFMgPC0gbWVyZ2UoZGZfdG9wTlBMUywgdmlwX21lYW5bLCAuKFNOUCwgU05QX2NvdW50KV0sIGJ5ID0gIlNOUCIpCmRmX3RvcE5QTFMkU05QX2NvdW50IDwtIGRmX3RvcE5QTFMkU05QX2NvdW50LzEwCgpgYGAKCmBgYHtyfQpnZ3Bsb3QoZGZfdG9wTlBMUywgYWVzKHggPSByZW9yZGVyKFNOUCxWSVApLCB5ID0gVklQLCBmaWxsID0gU05QX2NvdW50KSkgKwogIGdlb21fdmlvbGluKHRyaW0gPSBGQUxTRSkgKwogIGdlb21fYm94cGxvdCh3aWR0aCA9IDAuMSwgb3V0bGllci5zaXplID0gMC41KSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAibGlnaHRibHVlIiwgaGlnaCA9ICJkYXJrYmx1ZSIpICsKICB0aGVtZV9idygpICsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdCA9IDEpCiAgKSArCiAgbGFicygKICAgIHggPSAiU05QIiwKICAgIHkgPSAiVklQIiwKICAgIHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBkdSBWSVAgcG91ciBsZXMgbiA9IDUwIFNOUCBsZXMgcGx1cyBpbXBvcnRhbnRzIiwKICAgIGZpbGwgPSAiTmJyIGRlIHNlbGVjdGlvbiBkdSBTTlAiCiAgKQpgYGAKCmBgYHtyfQpoaXN0KHZpcF9tZWFuJFZJUF9tZWFuLCBicmVha3MgPSBzZXEobWluKHZpcF9tZWFuJFZJUF9tZWFuKSwgbWF4KHZpcF9tZWFuJFZJUF9tZWFuKSwgbGVuZ3RoLm91dCA9IDUwKSwKICAgICBtYWluID0gIkRpc3RyaWJ1dGlvbiBkZXMgc2NvcmVzIFZJUCBtb3llbnMiLAogICAgIHhsYWIgPSAic2NvcmVzIFZJUCBtb3llbnMiLCBjb2wgPSAibGlnaHRibHVlIiwgYm9yZGVyID0gIndoaXRlIikKYGBgCgojIExhc3NvCgpgYGB7cn0KbGlicmFyeShnbG1uZXQpCgojIEFzc3VyZXIgcXVlIHkgZXN0IG51bcOpcmlxdWUgZXQgWCBlc3QgdW5lIG1hdHJpY2UKeSA8LSBhcy5udW1lcmljKHBoZW5vJENJUkMyMDA5KQpYIDwtIGFzLm1hdHJpeChnZW5vKQoKdHJhaW5faW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih5LCBwID0gMC44LCBsaXN0ID0gRkFMU0UpClhfdHJhaW4gPC0gWFt0cmFpbl9pbmRleCwgXQpYX3Rlc3QgIDwtIFhbLXRyYWluX2luZGV4LCBdCnlfdHJhaW4gPC0geVt0cmFpbl9pbmRleF0KeV90ZXN0ICA8LSB5Wy10cmFpbl9pbmRleF0KCiMgQWp1c3RlbWVudCBkdSBMYXNzbyBhdmVjIHZhbGlkYXRpb24gY3JvaXPDqWUgcG91ciBjaG9pc2lyIGxhbWJkYQpzZXQuc2VlZCgxMjMpCgpjdl9sYXNzbyA8LSBjdi5nbG1uZXQoWF90cmFpbiwgeV90cmFpbiwgYWxwaGEgPSAxLCBuZm9sZHMgPSA1KQoKIyBBZmZpY2hlciBsYSBtZWlsbGV1cmUgdmFsZXVyIGRlIGxhbWJkYQpiZXN0X2xhbWJkYSA8LSBjdl9sYXNzbyRsYW1iZGEubWluCnByaW50KGJlc3RfbGFtYmRhKQoKIyBUcmFjZXIgbCdlcnJldXIgZGUgdmFsaWRhdGlvbiBjcm9pc8OpZQpwbG90KGN2X2xhc3NvKQoKIyBBanVzdGVyIGxlIG1vZMOobGUgZmluYWwgYXZlYyBsYW1iZGEgb3B0aW1hbApsYXNzb19tb2RlbCA8LSBnbG1uZXQoWF90cmFpbiwgeV90cmFpbiwgYWxwaGEgPSAxLCBsYW1iZGEgPSBiZXN0X2xhbWJkYSkKCiMgRXh0cmFpcmUgbGVzIGNvZWZmaWNpZW50cwpjb2VmKGxhc3NvX21vZGVsKQpgYGAKCmBgYHtyfQpwcmludChiZXN0X2xhbWJkYSkKYGBgCgpgYGB7cn0KeV9wcmVkIDwtIHByZWRpY3QobGFzc29fbW9kZWwsIG5ld3ggPSBYX3Rlc3QpCgojIENhbGN1bCBkdSBSwrIKU1NFIDwtIHN1bSgoeV90ZXN0IC0geV9wcmVkKV4yKSAgICAgICAgICAgIyBzb21tZSBkZXMgY2FycsOpcyBkZXMgZXJyZXVycwpTU1QgPC0gc3VtKCh5X3Rlc3QgLSBtZWFuKHkpKV4yKSAgICAgICAgICMgc29tbWUgdG90YWxlIGRlcyBjYXJyw6lzClIyIDwtIDEgLSBTU0UvU1NUCnByaW50KHNxcnQoU1NFKSkKUjIKYGBgCgpgYGB7cn0KY29lZl9kZiA8LSBkYXRhLmZyYW1lKAogIFNOUCA9IHJvd25hbWVzKGNvZWYobGFzc29fbW9kZWwpKSwKICBDb2VmZmljaWVudCA9IGFzLm51bWVyaWMoY29lZihsYXNzb19tb2RlbCkpCikKY29lZl9ub256ZXJvIDwtIGNvZWZfZGZbY29lZl9kZiRDb2VmZmljaWVudCAhPSAwLCBdCmNvZWZfbm9uemVyb19kdCA8LSBhcy5kYXRhLnRhYmxlKGNvZWZfbm9uemVybykKCiMgVG9wIE4gU05QIHBhciB2YWxldXIgYWJzb2x1ZSBkZXMgY29lZmZpY2llbnRzCk4gPC0gNTAKY29lZl9ub256ZXJvX2R0WywgYWJzX2NvZWYgOj0gYWJzKENvZWZmaWNpZW50KV0KdG9wTl9MYXNzbyA8LSBjb2VmX25vbnplcm9fZHRbb3JkZXIoLWFic19jb2VmKV1bMTpOLCAuKFNOUCwgQ29lZmZpY2llbnQpXQoKdG9wTl9MYXNzbwpgYGAKCiMgQ29tcGFyYWlzb24gc2VsZWN0aW9uIExhc3NvIGV0IFBMUwoKYGBge3J9Cgpjb21tb25fc25wcyA8LSBpbnRlcnNlY3QoZGZfdG9wTlBMUyRTTlAsIGNvZWZfbm9uemVybyRTTlApCgojIFNOUCBjb21tdW5zCmNvbW1vbl9zbnBzCmxlbmd0aChjb21tb25fc25wcykKCmBgYAoKIyByw6llZ3Jlc3Npb24gcmlkZ2UgYWRhcHRhdGl2ZQoKYGBge3J9CnkgPC0gYXMubnVtZXJpYyhwaGVubyRDSVJDMjAwOSkKWCA8LSBhcy5tYXRyaXgoZ2VubykKCiMgU3BsaXQgZXh0ZXJuZSB0cmFpbi90ZXN0CnNldC5zZWVkKDEyMykKdHJhaW5faW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih5LCBwID0gMC45LCBsaXN0ID0gRkFMU0UpClhfdHJhaW4gPC0gWFt0cmFpbl9pbmRleCwgXQpYX3Rlc3QgIDwtIFhbLXRyYWluX2luZGV4LCBdCnlfdHJhaW4gPC0geVt0cmFpbl9pbmRleF0KeV90ZXN0ICA8LSB5Wy10cmFpbl9pbmRleF0KCiMgU3BsaXQgaW50ZXJuZSBkdSB0cmFpbiBlbiBEMSBldCBEMiA6IDUwLzUwCnNldC5zZWVkKDEyMykKaWR4X0QxIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeV90cmFpbiwgcCA9IDAuNSwgbGlzdCA9IEZBTFNFKQoKWF9EMSA8LSBYX3RyYWluW2lkeF9EMSwgXQpYX0QyIDwtIFhfdHJhaW5bLWlkeF9EMSwgXQp5X0QxIDwtIHlfdHJhaW5baWR4X0QxXQp5X0QyIDwtIHlfdHJhaW5bLWlkeF9EMV0KYGBgCgojIyMgU1RFUCBJIDogU0NSRUVOSU5HIChFbGFzdGljIE5ldCBzdXIgRDEpCgpgYGB7cn0KYWxwaGEgPC0gMSAjIEVsYXN0aWMgTmV0IHNpIGFscGhhID0gMC41LCBMYXNzbyBzaSA9IDEsIHJpZGdlIHNpID0wIApzZXQuc2VlZCgxMjMpCmN2X2VuZXQgPC0gY3YuZ2xtbmV0KAogIFhfRDEsIHlfRDEsCiAgYWxwaGEgPSBhbHBoYSwgICAgICAgICAgICAgICAKICBuZm9sZHMgPSAxMCwKICBzdGFuZGFyZGl6ZSA9IFRSVUUKKQoKbGFtYmRhX2hhdCA8LSBjdl9lbmV0JGxhbWJkYS5taW4KYGBgCgpgYGB7cn0KbGFtYmRhX2hhdApgYGAKCmBgYHtyfQojIEFqdXN0ZW1lbnQgZmluYWwgCmVuZXRfbW9kZWwgPC0gZ2xtbmV0KFhfRDEsIHlfRDEsIGFscGhhID0gYWxwaGEsIGxhbWJkYSA9IDAuMDAyKQoKCmNvZWZzIDwtIGNvZWYoZW5ldF9tb2RlbCkKc2VsZWN0ZWQgPC0gd2hpY2goY29lZnNbLTFdICE9IDApICAgICAgICAgICMgaW5kaWNlcyBkZXMgU05QIHPDqWxlY3Rpb25uw6lzClNfaGF0IDwtIHNlbGVjdGVkCgpjYXQoIk5vbWJyZSBkZSB2YXJpYWJsZXMgc8OpbGVjdGlvbm7DqWVzIChzY3JlZW5pbmcpOiIsIGxlbmd0aChTX2hhdCksICJcbiIpCgojIFBvaWRzIGTDqXJpdsOpcyBkZXMgY29lZmZpY2llbnRzIEVsYXN0aWMgTmV0CmJldGFfaGF0IDwtIGFzLm51bWVyaWMoY29lZnNbLTFdKQp3ZWlnaHRzIDwtIGFicyhiZXRhX2hhdCkKd2VpZ2h0cyA8LSB3ZWlnaHRzW1NfaGF0XQp3ZWlnaHRzIDwtIHdlaWdodHMgLyBtYXgod2VpZ2h0cykgICAgICAgICAgIyBub3JtYWxpc2F0aW9uCmBgYAoKYGBge3J9CmxhbWJkYV9oYXQgPC0gMC4wMDIKYGBgCgojIyBTVEVQIElJIDogQ0xFQU5JTkcgKFJpZGdlIHN1ciBEMikKCmBgYHtyfQpYX0QyX3NlbCA8LSBYX0QyWywgU19oYXQsIGRyb3AgPSBGQUxTRV0KCiMgUG9uZMOpcmF0aW9uIFJpZGdlIDogbWV0dHJlIHBsdXMgZGUgcMOpbmFsaXTDqSBzdXIgbGVzIHBldGl0ZXMgzrJfRU4KcmlkZ2VfcGVuYWx0eSA8LSAxIC8gKHdlaWdodHMgKyAxZS02KQoKc2V0LnNlZWQoMTIzKQpjdl9yaWRnZSA8LSBjdi5nbG1uZXQoCiAgWF9EMl9zZWwsIHlfRDIsCiAgYWxwaGEgPSAwLCAgICAgICAgICAgICAgICAgICAgICMgYWxwaGEgPSAwIOKGkiBSaWRnZQogIG5mb2xkcyA9IDEwLAogIHBlbmFsdHkuZmFjdG9yID0gcmlkZ2VfcGVuYWx0eQopCgpsYW1iZGFfcmlkZ2UgPC0gY3ZfcmlkZ2UkbGFtYmRhLm1pbgoKIyBBanVzdGVtZW50IGZpbmFsIHN1ciBEMgpyaWRnZV9tb2RlbCA8LSBnbG1uZXQoCiAgWF9EMl9zZWwsIHlfRDIsCiAgYWxwaGEgPSAwLAogIGxhbWJkYSA9IGxhbWJkYV9yaWRnZSwKICBwZW5hbHR5LmZhY3RvciA9IHJpZGdlX3BlbmFsdHkKKQoKIyBDb2VmZmljaWVudHMgcmlkZ2UgZmluYXV4IChzdXIgRDIpCmJldGFfY2xlYW4gPC0gY29lZihyaWRnZV9tb2RlbCkKYGBgCgojIFNURVAgSUlJIDogUkVGSVQgRklOQUwgU1VSIEQgPSBEMSDiiKogRDIgKHRyYWluIGNvbXBsZXQpCgpgYGB7cn0KWF90cmFpbl9zZWwgPC0gWF90cmFpblssIFNfaGF0LCBkcm9wID0gRkFMU0VdCgpyaWRnZV9maW5hbCA8LSBnbG1uZXQoCiAgWF90cmFpbl9zZWwsIHlfdHJhaW4sCiAgYWxwaGEgPSAwLAogIGxhbWJkYSA9IGxhbWJkYV9yaWRnZSwKICBwZW5hbHR5LmZhY3RvciA9IHJpZGdlX3BlbmFsdHkKKQoKIyBQcsOpZGljdGlvbnMgc3VyIGxlIHRlc3QgZXh0ZXJuZQpYX3Rlc3Rfc2VsIDwtIFhfdGVzdFssIFNfaGF0LCBkcm9wID0gRkFMU0VdCnlfcHJlZCA8LSBwcmVkaWN0KHJpZGdlX2ZpbmFsLCBuZXd4ID0gWF90ZXN0X3NlbCkKCiMgUsKyIGV4dGVybmUKUjJfdGVzdCA8LSAxIC0gc3VtKCh5X3Rlc3QgLSB5X3ByZWQpXjIpIC8gc3VtKCh5X3Rlc3QgLSBtZWFuKHlfdGVzdCkpXjIpCmNhdCgiUjIgZXh0ZXJuZSA6IiwgUjJfdGVzdCwgIlxuIikKYGBgCg==